.COMMENT ;
 Floppy Disc Test for Z-80 CP/M Systems
 Dr. Dobb's:  April 1981

Version 1.0

The purchaser may freely create any number of copies of this
program on magnetic media as necessary to support his computer
system(s). Further resale of this program is prohibited.

COPYRIGHT (c) 1980 by
Ray Duncan
Laboratory Microsystems
4147 Beethoven Street
Los Angeles. CA 90066
;

CPM		EQU	5H	; References to..
WBOOT		EQU	0H	; ..operating system
;				  References to..
CR		EQU	0DH	; ..ASCII characters
LF		EQU	0AH
FF		EQU	0CH
TAB		EQU	09H
;
; Parameters for disk (supplied as single density, soft sect.)
; 
$DRVF		EQU	1H	; First drive to allow
;				  ..testing (0=A, 1=B etc.)
$DRVL		EQU	3H	; Last drive to allow test
$TRKF		EQU	0H	; First track
$TRKL		EQU	76	; Last track
$SECF		EQU	1	; First sector
$SECL		EQU	26	; Last sector
$BPS		EQU	128	; Bytes per sector
$BPT		EQU	$BPS*$SECL ; Bytes per sector
$DIG		EQU	2	; Number of digits to accept
				; ..in track and sector assign.
				; ..(Should be set larger for
				; .. devices having track/
				; .. sector numbers >99 )
;
$VER		EQU	1	; Program version
$REV		EQU	0	; .. revision
;
		.Z80
		ASEG
;
		ORG	100H
START:
		JP	DTST	; Enter from CPM
;
; Global varables for use by all routines
;
PASS:		DW	0	; Current pass
ERRORS:		DW	0	; Error count for pass
;
; The following variables are used by RDBUF and WRTBUF to
;  address the disk, and display failing disk addresses.
;
DRV:		DB	0	; Drive to test
TRK:		DW	0	; Current track
SEC:		DW	0	; Current sector
BUFFER:		DW	0	; Current memory address
IOLEN:		DW	0	; Bytes last transferred
;
; The following variables define the area to be tested on
;  the selected drive.
;
TRKF:		DW	0	; First track to test
TRKL:		DW	0	; Last track to test
SECF:		DW	0	; First sector to test
SECL:		DW	0	; Last sector to test
;
; The following variables define the test mode
;
BYPASS:		DB	0	; 0=do not bypass error item.
				; 1=bypass error itemization,
				; ..print total errors per pass.
SKEW:		DB	0	; 0=no sector skew
				; 1=use sector skew for speed
LIST:		DB	0	; 0=print errors on terminal
				; 1=print errors on list dev.
LOCKIO:		DB	0	; 0=no lock
				; 1=lock on read
				; 2=lock on write
RESTOR:		DB	0	; 0=do not restore original
				; ..data, 1=restore original
				; ..data on disk.
LOCKPT:		DB	0	; 0=use variable test data
				; ..pattern, 1=lock on user
				; ..supplied data pattern.
PATTRN:		DB	0	; Contains user supplied
				; ..8 bit data pattern.
PASSL:		DW	0	; Last pass to do on this test
DIGITS:		DB	$DIG	; Max. number of digits to be
				; ..accepted during hex. or dec.
				; ..numeric input.
XTRAN:		DW	SECTRB	; Address of sector XLATE table
;
; Disk test --- main control
;
DTST:				; Entry from CPM
	LD	DE,DTSTA 	; Print program title
	LD	C,9
	CALL	CPM
	LD	HL,(CPM+1)
	LD	DE,BUFFEND
	OR	A		; Make sure enough user
	SBC	HL,DE		; ..memory to execute test
	JR	NC,DTST01
	LD	DE,DTSTS 	; Not enough memory
	LD	C,9		; Print warning and exit
	CALL	CPM
	JP	WBOOT
DTST01:				; Ckeck CPM version
	LD	C,12
	CALL	CPM
	LD	A,L		; Make sure 2.x
	AND	0F0H
	CP	20H
	JR	Z,DTST02
	LD	DE,DTSTZ	; Not CPM 2.x, print
	LD	C,9		; ..error message and quit
	CALL	CPM
	JP	WBOOT
DTST02:				; Initialize variables
	XOR	A
	LD	(BYPASS),A
	LD	(SKEW),A
	LD	(LIST),A
	LD	(LOCKIO),A
	LD	(RESTOR),A
	LD	(LOCKPT),A
	LD	(PASS),A
	LD	(PASS+1),A
	LD	(ERRORS),A
	LD	(ERRORS+1),A
				; Now set up test configuration
	LD	DE,DTSTB
	CALL	GETYN		; Itemize errors?
	CP	'y'
	JR	Z,DTST03 	; Yes
	LD	A,1		; No
	LD	(BYPASS),A
	JR	DTST04		; Skip query for output device
				; ..since errors will not be listed
DTST03:				; Audit errors on console
	LD	DE,DTSTC 	; ..or line printer?
	CALL	GETL
	CP	'c'
	JR	Z,DTST04 	; c=use console
	CP	'p'
	CALL	NZ,QUERY
	JR	NZ,DTST03 	; No match, try again
	LD	A,1		; p=use line printer
	LD	(LIST),A
DTST04:				; Lock on read or write?
	LD	DE,DTSTD
	CALL	GETL
	CP	'n'		; n=no locks
	JR	Z,DTST06
	CP	'r'		; r=lock on read
	JR	NZ,DTST05
	LD	A,1
	LD	(LOCKIO),A
	JR	DTST12		; Bypass queries about restore
				; ..mode and data pattern, since
				; ..we are locked in read mode
DTST05:
	CP	'w'		; w=lock on write
	CALL	NZ,QUERY
	JR	NZ,DTST04 	; No match, try again
	LD	A,2
	LD	(LOCKIO),A
	JR	DTST08		; Bypass restore question
				; ..since we are locked in
				; ..write mode
DTST06:
	LD	DE,DTSTE 	; Restore user data?
	CALL	GETYN
	CP	'y'		; y=restore
	JR	NZ,DTST08
	LD	A,1		; n=do not restore
	LD	(RESTOR),A
DTST08:
	LD	DE,DTSTF 	; Lock on data pattern?
	CALL	GETYN
	CP	'n'
	JR	Z,DTST12	; n=use variable pattern
	LD	A,1		; y=lock on pattern
	LD	(LOCKPT),A	; ..supplied by operator
	LD	DE,DTSTG	; Accept data pattern
	CALL	GETH		; ..from keyboard
	LD	(PATTRN),A
DTST12:
	LD	DE,DTSTH	; Select drive to be tested
	CALL	GETL
	SUB	'a'		; Convert to logical number
	CP	$DRVF		; Make sure its legal
	CALL	C,QUERY
	JR	C,DTST12	; Too small, try again
	CP	$DRVL+1
	CALL	NC,QUERY
	JR	NC,DTST12	; Too large, try again
	LD	(DRV),A		; Save drive assignment
	ADD	A,'A'		; Also format for output
	LD	(DTSTI1),A
	LD	DE,DTSTI	; Confirm selected drive?
	CALL	GETYN
	CP	'n'
	JR	Z,DTST12	; Not confirmed, try again
	LD	HL,$TRKF	; Initialize track limits
	LD	(TRKF),HL
	LD	HL,$TRKL
	LD	(TRKL),HL
DTST15:
	LD	DE,DTSTJ	; Test all tracks?
	CALL	GETYN
	CP	'y'		; y=use all of them
	JR	Z,DTST20	; n=user wants to specify
DTST17:
	LD	DE,DTSTK	; Enter first track to test
	CALL	GETN
	LD	(TRKF),HL	; Save it
	LD	DE,DTSTL	; Enter last track to test
	CALL	GETN
	LD	(TRKL),HL	; Save it
	LD	DE,(TRKF)	; Make sure first
	OR	A		; ..track<=last track
	SBC	HL,DE
	CALL	C,QUERY		; Wrong, start again
	JR	C,DTST17
DTST20:
	LD	HL,$SECF	; Initialize sector limits
	LD	(SECF),HL
	LD	HL,$SECL
	LD	(SECL),HL
DTST22:
	LD	DE,DTSTM	; Use all sectors of each track
	CALL	GETYN
	CP	'y'		; y=use all sectors
	JR	Z,DTST26	; n=user wants to specify
DTST24:
	LD	DE,DTSTN	; Enter first sector to test
	CALL	GETN
	LD	(SECF),HL	; Save it
	LD	DE,DTSTO	; Enter last sector to test
	CALL	GETN
	LD	(SECL),HL	; Save it
	LD	DE,(SECF)	; Make sure first
	OR	A		; ..sector<=last sector
	SBC	HL,DE
	CALL	C,QUERY
	JR	C,DTST24	; Error, start again
DTST26:				; All variables set up now --
	LD	DE,DTSTP	; ..how many test passes
	CALL	GETN		; ..should be made?
	LD	(PASSL),HL	; Save # of passes
	LD	DE,DTSTT	; Print advisory message
	LD	C,9		; ..as test begins
	CALL	CPM
	LD	DE,DTSTU	; Remind user whether he is
	LD	A,(RESTOR)	; ..using restore mode
	OR	A
	JR	Z,DTST32
	LD	DE,DTSTV
DTST32:
	LD	C,9
	CALL	CPM
DTST40:				; Begin a pass
	LD	HL,(TRKF)
	LD	(TRK),HL	; Initialize current track
DTST42:				; Process next track
	LD	C,6		; Check for interruption
	LD	E,0FFH
	CALL	CPM		; ..from console
	OR	A
	JP	NZ,DTST94	; Break detected, quit
	LD	A,(RESTOR)
	OR	A		; Is this restore mode?
	JR	Z,DTST45	; No jump
	LD	HL,BUFF3	; Yes, save current disk
	LD	DE,MERR1	; ..contents
	CALL	RDBUF
DTST45:
	LD	A,(LOCKIO)
	CP	1		; Is this lock on read?
	JR	Z,DTST47	; Yes jump
	LD	HL,BUFF1	; Set up test pattern
	LD	DE,$BPT
	CALL	BUFPAT
	LD	HL,BUFF1	; Write test pattern
	LD	DE,MERR2
	CALL	WTBUF
DTST47:
	LD	A,(LOCKIO)
	CP	2		; Is this lock on write?
	JR	Z,DTST70	; Yes, jump
	LD	HL,BUFF2	; Read back test pattern
				; (or just read existing data
				; ..if locked on read)
	LD	DE,MERR3
	CALL	RDBUF
DTST50:
	LD	A,(LOCKIO)
	OR	A		; Is this lock on read or write
	JR	NZ,DTST70	; Yes, jump
				; No, compare test data
	LD	HL,BUFF1	; ..written, to data read
	LD	DE,BUFF2	; ..back from disk. If
	LD	BC,MERR4	; ..difference found,
	CALL	BUFCMP		; ..print error message
DTST70:
	LD	A,(RESTOR)
	OR	A		; Using restore mode?
	JR	Z,DTST80	; No, jump
				; Yes, write back user's data
	LD	HL,BUFF3
	LD	DE,MERR6
	CALL	WTBUF
	LD	HL,BUFF1	; Verify that
	LD	DE,MERR7	; ..it was rewritten OK
	CALL	RDBUF
	LD	HL,BUFF1
	LD	DE,BUFF3
	LD	BC,MERR5	; Check restored data
	CALL	BUFCMP		; If difference found, print
				; ..'data cannot be restored'
DTST80:
	LD	DE,(TRK)	; Advance current track
	INC	DE
	LD	(TRK),DE
	LD	HL,(TRKL)
	OR	A		; Done with all tracks?
	SBC	HL,DE
	JP	NC,DTST42	; No, process another
DTST90:				; End of pass
	LD	BC,(PASS)
	INC	BC		; Count passes
	LD	(PASS),BC
	LD	HL,DTSTR1
	CALL	CONV		; Convert pass #
	LD	BC,(ERRORS)
	LD	HL,DTSTR2
	CALL	CONV		; Convert error count
	LD	DE,DTSTR	; Print pass and errors
	LD	C,9		; ..on console
	CALL	CPM
	LD	A,(LIST)	; Also using printer?
	OR	A
	JR	Z,DTST92	; No, jump
				; Yes, also send pass and error
				; ..count to printer
	LD	HL,DTSTR
	CALL	PERR9
DTST92:				; Reset error count
	XOR	A
	LD	(ERRORS),A
	LD	(ERRORS+1),A
	LD	HL,(PASS)
	LD	DE,(PASSL)
	OR	A		; Are enough passes done?
	SBC	HL,DE
	JP	C,DTST40	; Not yet, loop done with all
DTST94:				; ..passes
	LD	DE,DTSTW	; Ask whether to exit
	CALL	GETL		; ..or to continue test
	CP	'c'		; c=continue
	JP	Z,DTST
	CP	'e'		; e=exit
	JR	NZ,DTST94	; If no match, try again
	LD	DE,DTSTX	; Print goodbye
	LD	C,9
	CALL	CPM		; ..and return control
	JP	WBOOT		; ..to CP/M
;
; Routines to read and write up to one track
;
RDBUF:				; Read current track from
				; ..secf to secl
				; Call hl=buffer base addr
				; ..   de=error msg addr
	LD	(RDBUFA),DE	; Save message address
	LD	(BUFFER),HL	; Save buffer address
	LD	HL,0		; Initialize transfer byte
	LD	(IOLEN),HL	; ..count
	CALL	SELDSK		; Select disk
	LD	HL,(SECF)
	LD	(SEC),HL	; Initialize current sector
RDBUF1:
	CALL	SETIO		; Setup track, sector, memory
	CALL	READ		; Now request transfer
	OR	A		; Was I/O successful?
	JR	Z,RDBUF2	; No error, jump
	LD	DE,(RDBUFA)
	CALL	PERR		; I/O error, audit it
RDBUF2:
	CALL	RWADV		; Advance sector address
	JR	NC,RDBUF1	; Not done, read another
	RET			; Back to caller
;
RDBUFA:	DW	0		; Address of error message
;
WTBUF:				; Write current track
				; ..from secf to secl
				; Call de=error msg addr
				; ..   hl=buffer base addr
	LD	(WTBUFA),DE	; Save message address
	LD	(BUFFER),HL	; Save memory addr
	LD	HL,0		; Initialize transfer
	LD	(IOLEN),HL	; ..byte count
	CALL	SELDSK		; Select disk drive
	LD	HL,(SECF)
	LD	(SEC),HL	; Initialize current sector
WTBUF1:
	CALL	SETIO		; Set track, sector, memory
	CALL	WRITE		; Request disk write
	OR	A		; Any I/O errors?
	JR	Z,WTBUF2	; No, jump
	LD	DE,(WTBUFA)
	CALL	PERR		; Error, audit it
WTBUF2:
	CALL	RWADV		; Advance sector address
	JR	NC,WTBUF1	; Not done, write another
	RET			; Back to caller
;
WTBUFA	EQU	RDBUFA		; Save address of error msg
;
RWADV:				; Advance sector and memory addr
	LD	DE,$BPS		; de<--bytes per sector
	LD	HL,(BUFFER)
	ADD	HL,DE		; Update buffer address
	LD	(BUFFER),HL
	LD	HL,(IOLEN)
	ADD	HL,DE		; Count bytes transferred
	LD	(IOLEN),HL
	LD	DE,(SEC)	; Advance current sector
	INC	DE
	LD	(SEC),DE
	LD	HL,(SECL)
	OR	A		; Done with all sectors?
	SBC	HL,DE		; Exit with carry set if done
	RET
;
; Set up buffer with test pattern
;
BUFPAT:				; Call hl=buffer base address
				; ..   de=byte length to set up
	LD	A,(LOCKPT)
	OR	A		; Are we locked on user specified
				; ..data pattern?
	JR	NZ,BUFPA2	; Yes, jump
BUFPA1:
	LD	A,R		; Read refresh register
	XOR	H
	ADD	A,L		; Make data a function of memory
				; ..address
	LD	(HL),A		; ..and store it
	INC	HL		; Advance buffer address
	DEC	DE		; Count bytes stored
	LD	A,D		; Done yet?
	OR	E
	JR	NZ,BUFPA1	; No, loop
	RET
BUFPA2:
	LD	A,(PATTRN)	; User specified parrern
	LD	(HL),A		; Store one byte
	INC	HL		; Advance buffer pointer
	DEC	DE		; Count bytes stored
	LD	A,D		; Done yet?
	OR	E
	JR	NZ,BUFPA2	; Not done, loop
	RET			; Exit
;
;Compare specified buffer and print error message if difference found
;
BUFCMP:				; Call bc=error msg addr
				; ..   de=1st buffer addr
				; ..   hl=2nd buffer addr
	LD	(BUFCMA),BC	; Save message addr
	LD	(BUFCMB),HL	; Save base of buffer
	LD	BC,(IOLEN)	; Length to compare
BUFCM1:
	LD	A,(DE)		; Fetch byte from 1st buffer
	CP	(HL)		; Compare it to 2nd buffer
	JR	NZ,BUFCM3	; Difference found, jump
BUFCM2:
	INC	HL		; Advance buffer addresses
	INC	DE
	DEC	BC		; Count bytes
	LD	A,B		; Done yet?
	OR	C
	JR	NZ,BUFCM1	; No, loop
	RET			; Back to caller
BUFCM3:				; Difference found, print
				; ..error audit trail
	PUSH	BC		; First save registers
	PUSH	DE
	PUSH	HL
	LD	DE,(BUFCMB)
	OR	A
	SBC	HL,DE		; Find a buffer offset
	PUSH	HL		; Now divide by bytes per
	POP	BC		; ..sector to find relative
	LD	DE,$BPS		; ..sector number
	CALL	DIV
	LD	HL,(SECF)
	ADD	HL,BC		; Add relative sector to first
				; ..sector to find actual
				; ..address for use by PERR
	LD	(SEC),HL
	LD	DE,(BUFCMA)
	CALL	PERR		; Now audit error
	POP	HL		; Restore registers
	POP	DE
	POP	BC
BUFCM4:				; Advance memory address out of
				; ..this sector where an error
				; ..was found
	INC	HL		; Bump buffer addresses
	INC	DE
	DEC	BC		; Done with all data area?
	LD	A,B
	OR	C
	RET	Z		; Yes, exit compare routine
	LD	A,L		; Check if on new sector
	AND	$BPS-1		; ..boundary
	JR	Z,BUFCM1	; Found it, go compare more data
	JR	BUFCM4		; Keep adv until sector boundary
;
BUFCMA:	DW	0		; Address of error message
BUFCMB:	DW	0		; Base buffer addresses
;
; Error printing routine, prints pass, drive, track, sector
;  and message specified by caller on the console device.
;
PERR:				; Call de=error message address
	LD	A,(BYPASS)
	OR	A		; Is error itemization bypass set
	JR	NZ,PERR2	; Yes, skip printing and go count
	LD	(PERRA),DE	; Save message address
	LD	BC,(PASS)
	INC	BC
	LD	HL,PERRC	; Convert current pass
	CALL	CONV
	LD	A,(DRV)		; Form drive name
	ADD	A,'A'
	LD	(PERRD),A
	LD	BC,(TRK)	; Convert current track
	LD	HL,PERRE
	CALL	CONV
	LD	BC,(SEC)	; Convert current sector
	LD	A,(SKEW)	; Is skew in effect?
	OR	A
	JR	Z,PERR0		; No
	CALL	SECTRAN		; Yes, translate sector
PERR0:
	LD	HL,PERRF
	CALL	CONV
	LD	A,(LIST)	; Should output be on
	OR	A		; ..console or printer?
	JR	NZ,PERR3	; Jump, use printer, fall thru
	LD	HL,(ERRORS)	; ..use console
	LD	A,H		; Is this the first error?
	OR	L
	JR	NZ,PERR1	; No, jump
	LD	DE,DTSTQ	; Print title for errors
	LD	C,9
	CALL	CPM
PERR1:
	LD	DE,PERRB	; Print disk address
	LD	C,9
	CALL	CPM
	LD	DE,(PERRA)	; Print error type
	LD	C,9
	CALL	CPM
PERR2:
	LD	HL,(ERRORS)	; Count errors
	INC	HL
	LD	(ERRORS),HL
	RET			; Back to caller
PERR3:				; Errors to printer
	LD	HL,(ERRORS)
	LD	A,H		; Is this 1st error to be
	OR	L		; ..printed this pass?
	JR	NZ,PERR4	; No, jump
	LD	HL,DTSTQ	; Yes, print title
	CALL	PERR9
PERR4:
	LD	HL,PERRB	; Print disk address
	CALL	PERR9
	LD	HL,(PERRA)
	CALL	PERR9		; Print error type
	JR	PERR2		; Go count errors
PERR9:				; Send a string terminated by
				; ..'$' to list device
	LD	A,(HL)		; Fetch next character
	CP	'$'		; Is it terminator?
	RET	Z		; Yes, exit
	PUSH	HL		; Save string address
	LD	E,A		; Send this character
	LD	C,5
	CALL	CPM
	POP	HL		; Restore string address
	INC	HL		; ..and increment it
	JR	PERR9		; Check next character
;
PERRA:	DW	0		; Addrs of msg describing error
PERRB:	DB	CR,LF
PERRC:	DB	'nnnn    '	; Pass #
PERRD:	DB	'n     '	; Drive
PERRE:	DB	'nnnn   '	; Track
PERRF:	DB	'nnnn   $'	; Sector
;
; Disk interface to CP/M BIOS
;
SELDSK:
	LD	A,(DRV)		; Select disk drive
	LD	C,A
	LD	DE,24
JPBIOS:				; This routine links to the
	LD	HL,(WBOOT+1)	; ..desired routine through
	ADD	HL,DE		; ..the standard CP/M
	JP	(HL)		; ..BIOS jump table
SETTRK:
	LD	BC,(TRK)	; Select track
	LD	DE,27
	JR	JPBIOS
SETSEC:
	LD	BC,(SEC)	; Select sector
	LD	DE,30
	LD	A,(SKEW)	; Use sector skew?
	OR	A
	JR	Z,JPBIOS	; No
	CALL	SECTRAN		; Translate sector addr
	JR	JPBIOS
SETDMA:
	LD	BC,(BUFFER)	; Set memory addr
	LD	DE,33
	JR	JPBIOS
SETIO:
	CALL	SETTRK		; Set up track, sector,
	CALL	SETSEC		; ..and memory address
	CALL	SETDMA		; ..for subsequent read
	RET			; ..or write
READ:
	LD	DE,36		; Read one disk sector
	JR	JPBIOS
WRITE:
	LD	DE,39		; Write one disk sector
	JR	JPBIOS
SECTRAN:			; Translate logical to physical
				; Call bc=logical sector
				; ..   hl=physical sector
	PUSH	HL
	LD	HL,SECTRB-1
	ADD	HL,BC
	LD	C,(HL)
	POP	HL
	RET
;
SECTRB:		DB	1,7,13,19,25	; Table built
		DB	5,11,17,23	; ..with skew
		DB	3,9,15,21	; ..factor = 6
		DB	2,8,14,20,26	; 128 byte sector
		DB	6,12,18,24
		DB	4,10,16,22
;
; Messages for test initialization and error printing
;
DTSTA:		DB	CR,LF,LF
		DB	'Z-80 Floppy Disk '
		DB	'Test version '
		DB	$VER+'0','.'
		DB	$REV+'0',CR,LF
		DB	'(c) 1980 Laboratory '
		DB	'Microsystems',CR,LF,'$'
DTSTB:		DB	CR,LF,'Itemize '
		DB	'errors?    $'
DTSTC:		DB	CR,LF,'Use '
		DB	'console or printer'
		DB	'? (C/P) $'
DTSTD:		DB	CR,LF,'Lock on read '
		DB	'or write? (N/R/W) $'
DTSTE:		DB	CR,LF,'Restore '
		DB	'original data? $'
DTSTF:		DB	CR,LF,'Lock on '
		DB	'data pattern? $'
DTSTG:		DB	CR,LF,'Enter data '
		DB	'pattern, hex 00-FF$'
DTSTH:		DB	CR,LF,'Drive '
		DB	'to be tested '
		DB	'(',$DRVF+'A','-'
		DB	$DRVL+'A',') $'
DTSTI:		DB	CR,LF,'Confirm: test drive '	
DTSTI1:		DB	'X ? $'
DTSTJ:		DB	CR,LF,'Test all '
		DB	'tracks?    $'
DTSTK:		DB	CR,LF,'First '
		DB	'track to test      $'
DTSTL:		DB	CR,LF,'Last '
		DB	'track to test      $'
DTSTM:		DB	CR,LF,'Test all '
		DB	'sectors?    $'
DTSTN:		DB	CR,LF,'First '
		DB	'sector to test    $'
DTSTO:		DB	CR,LF,'Last '
		DB	'sector to test    $'
DTSTP:		DB	CR,LF,'How many '
		DB	'test passes?    $'
DTSTQ:		DB	CR,LF,LF,'Pass  '
		DB	'Drive  Track  '
		DB	'Sector  Error-type'
		DB	CR,LF,'$'
DTSTR:		DB	CR,LF,LF,'Pass '
DTSTR1:		DB	'nnnn complete, '
DTSTR2:		DB	'nnnn errors.'
		DB	CR,LF,'$'
DTSTS:		DB	CR,LF,'Not enough '
		DB	'memory to execute.'
		DB	CR,LF,'$'
DTSTT:		DB	CR,LF,LF,'Beginning '
		DB	'disk test - push '
		DB	'any key to abort '
		DB	'program. ',CR,LF,'$'
DTSTU:		DB	'WARNING: user data '
		DB	'will not be '
		DB	'restored. ',CR,LF,'$'
DTSTV:		DB	'User data will be '
		DB	'restored. ',CR,LF,'$'
DTSTW:		DB	CR,LF,'Continue or '
		DB	'exit test? (C/E)$'
DTSTX:		DB	CR,LF,LF
		DB	'Goodbye. ',CR,LF,'$'
DTSTY:		DB	CR,LF,'Use sector '
		DB	'skew?  $'
DTSTZ:		DB	CR,LF,'Need CP/M 2.x '
		DB	'to execute. ',CR,LF,'$'
MERR1:		DB	'read error - original data$'
MERR2:		DB	'write error - test pattern$'
MERR3:		DB	'read error - test pattern$'
MERR4:		DB	'compare error - test pattern$'
MERR5:		DB	'original data cannot '
		DB	'be restored$'
MERR6:		DB	'write error - restore phase$'
MERR7:		DB	'read error - restore phase$'
;
; Utility and console input routines
;
GETYN:				; Get y or n response from operator
				; Call de= address of cue
				; ..    a=y or n
	PUSH	DE		; Save cue address
	LD	C,9		; Print cue message
	CALL	CPM
	LD	DE,GETYNA	; Print possible answers
	LD	C,9
	CALL	CPM
	CALL	GETCHAR		; Get a character from console
	OR	20H		; Fold to lowercase
	POP	DE		; Restore cue address
				; ..in case needed again
	CP	'y'		; Make sure response is OK
	RET	Z		; Exit if y
	CP	'n'
	RET	Z		; Exit if n
	PUSH	DE
	CALL	QUERY		; Print '?' if not
	POP	DE		; ..y or n, try again
	JR	GETYN
;
GETYNA:	DB	'(Y/N) ',TAB,'> $'
;
GETL:				; Get any response from operator
				; Call de=cue address
				; ..    a=ASCII character
	LD	C,9		; Print cue message
	CALL	CPM
	LD	DE,GETLA	; Tab and print
	LD	C,9		; ..cue mark
	CALL	CPM
	CALL	GETCHAR		; Read console
	OR	20H		; Fold lowercase
	RET
;
GETLA:	DB	TAB,'> $'
;
GETN:				; Get a decimal # from the console
				; Call de=cue address
				; ..return hl=number
	PUSH	DE		; Save cue message address
	LD	C,9
	CALL	CPM		; Print cue message
	LD	DE,GETNA	; Print tab and cue mark
	LD	C,9
	CALL	CPM
	LD	HL,0		; Initialize forming answer
	LD	A,(DIGITS)
	LD	B,A		; Total chars allowed to be input
GETN1:
	PUSH	HL		; Save answer
	PUSH	BC		; Save char count
	CALL	GETCHAR		; Read console
	POP	BC		; Restore char count
	POP	HL		; Restore forming answer
	CP	CR		; Is this return?
	JR	Z,GETN9		; Yes, exit with answer
	CP	'0'		; Is this legal char.?
	JR	C,GETN3		; No, jump
	CP	'9'+1		; Is this legal char.?
	JR	NC,GETN3	; No, jump
	AND	0FH		; Isolate bottom 4 bits
	PUSH	HL		; Previous data * 10
	POP	DE
	ADD	HL,HL		; *2
	ADD	HL,HL		; *4
	ADD	HL,DE		; *5
	ADD	HL,HL		; *10
	LD	E,A		; Now add in this digit
	LD	D,0
	ADD	HL,DE
	DJNZ	GETN1		; Count characters accepted
	JR	GETN9		; Enough accepted, exit
GETN3:				; Illegal character detected
	CALL	QUERY		; Print '?' and
	POP	DE		; ..restart input
	JR	GETN
GETN9:				; Input complete, clean
	POP	DE		; ..stack and exit with
	RET			; ..answer in (hl)
;
GETNA:	DB	TAB,'> $'
GETNB:	DB	'?$'
;
GETH:				; Get $dig hex digits from kbd
				; Call de=cue address
				; Return a=lower 8 bits of #
				; ..    hl=entire 16 bit #
	PUSH	DE		; Save cue address
	LD	C,9
	CALL	CPM		; Print cue message
	LD	DE,GETHA	; Print tab and cue mark
	LD	C,9
	CALL	CPM
	LD	HL,0		; Initialize forming answer
	LD	A,(DIGITS)
	LD	B,A		; Max digits to accept
GETH1:
	PUSH	BC		; Save registers
	PUSH	HL
	CALL	GETCHAR		; Read console
	POP	HL
	POP	BC		; Restore registers
	CP	CR		; If <CR> exit
	JR	Z,GETH25
	CP	'0'		; Make sure its legal
	JR	C,GETH3		; No, jump
	CP	'9'+1		; If alpha fold to
	JR	C,GETH15	; ..lowercase
	OR	20H
GETH15:
	CP	'f'+1		; Make sure its legal
	JR	NC,GETH3	; No, jump
	CP	'a'		; Check if alpha
	JR	C,GETH2		; Jump if 0-9
	ADD	A,9		; Add correction
GETH2:
	AND	0FH
	ADD	HL,HL		; Previous data *16
	ADD	HL,HL		; (Left shift 4 bits)
	ADD	HL,HL
	ADD	HL,HL
	ADD	A,L		; Add this char to it
	LD	L,A		; ..forming result
	DJNZ	GETH1		; Keep reading console
GETH25:
	POP	DE		; Clean up stack
	LD	A,L		; Put lower 8 bits of answer
				; ..in 'A'. (incase exit by <CR>)
	RET
GETH3:
	CALL	QUERY		; Print '?'
	POP	DE		; ..then restart input
	JR	GETH
;
GETHA:	DB	TAB,'> $'
;
QUERY:
	PUSH	AF		; Save flags
	LD	C,9		; Print '?'
	LD	DE,QUERYA
	CALL	CPM
	POP	AF		; Restore flags
	RET
;
QUERYA:	DB	' ?$'
;
GETCHAR:			; Get 1 char from console via
				; ..raw input mode. Do not echo
				; ..<CR>
	LD	E,0FFH		
	LD	C,6
	CALL	CPM		; Read console
	OR	A		; Anything there?
	JR	Z,GETCHAR	; No, try again
	CP	CR		; Is it <CR>?
	RET	Z		; Yes
	PUSH	AF		; No, echo it
	LD	E,A
	LD	C,6
	CALL	CPM
	POP	AF		; Restore 'A' and exit
	RET
CONV:				; Convert binary to decimal ASCII
				; Call bc=binary, in range 0000-9999
				; ..   hl=first byte addr to store
	LD	DE,1000
	CALL	DIV
	CALL	CONV9		; Thousands digit
	LD	DE,100
	CALL	DIV
	CALL	CONV9		; Hundreds digit
	LD	DE,10
	CALL	DIV
	CALL	CONV9		; Tens digit
	CALL	CONV9		; Units
	RET			; Back to caller
CONV9:
	LD	A,C		; Turn quotient into
	ADD	A,'0'		; ..ASCII char and
	LD	(HL),A		; ..store it
	INC	HL		; Bump output pointer
	PUSH	DE		; bc<--remainder
	POP	BC
	RET
DIV:				; Single precision divide
				; Call bc=numerator
				; ..   de=divisor
				; Return bc=quotient
				; ..     de=remainder
	PUSH	HL
	LD	HL,0
	OR	A
	SBC	HL,DE
	EX	DE,HL
	LD	HL,0
	LD	A,17
DIV0:
	PUSH	HL
	ADD	HL,DE
	JR	NC,DIV1
	EX	(SP),HL
DIV1:
	POP	HL
	PUSH	AF
	RL	C
	RL	B
	RL	L
	RL	H
	POP	AF
	DEC	A
	JR	NZ,DIV0
	OR	A
	RR	H
	RR	L
	EX	DE,HL
	POP	HL
	RET
;
BUFF1		EQU	1000H		; Disk buffers
BUFF2		EQU	$BPT*2+BUFF1
BUFF3		EQU	$BPT*2+BUFF2
BUFFEND		EQU	$BPT*2+BUFF3
;
		END	100H
